package solver

import (
	"fmt"
	"log"
	"strings"
)

func (g *Grid) Skycraper() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.skycraperLines(cand, g.rows) {
			used = true
		}
		if g.skycraperLines(cand, g.cols) {
			used = true
		}
	}
	return
}

func logTurbotFish(value int, cells []*Cell, name string) {
	var b strings.Builder

	fmt.Fprintf(&b, "r%vc%v==", cells[0].rowNum, cells[0].colNum)
	fmt.Fprintf(&b, "r%vc%v--", cells[1].rowNum, cells[1].colNum)
	fmt.Fprintf(&b, "r%vc%v==", cells[2].rowNum, cells[2].colNum)
	fmt.Fprintf(&b, "r%vc%v", cells[3].rowNum, cells[3].colNum)
	log.Printf("%v %v %v", b.String(), value, name)
}

func (g *Grid) skycraperLines(cand int, lines [9]House) (used bool) {
	lineNums := chooseHousesWithCand(lines, cand)
	for _, lineNums := range combine(lineNums, 2) {
		cells := verifyScrcraper(cand, lines[lineNums[0]], lines[lineNums[1]])
		if len(cells) != 4 {
			continue
		}
		if g.clearupSeenFromCells(cand, []*Cell{cells[0], cells[3]}) {
			used = true
			logTurbotFish(cand+1, cells, "Skycraper")
		}
	}
	return
}

func chooseHousesWithCand(lines [9]House, cand int) []int {
	lineNums := []int{}
	for i, line := range lines {
		if len(intsFromBools(boolCellNums(line, cand))) == 2 {
			lineNums = append(lineNums, i)
		}
	}
	return lineNums
}

func verifyScrcraper(cand int, line1, line2 House) []*Cell {
	cellNums1 := intsFromBools(boolCellNums(line1, cand))
	cellNums2 := intsFromBools(boolCellNums(line2, cand))
	for i, cellNum1 := range cellNums1 {
		for j, cellNum2 := range cellNums2 {
			if cellNum1 == cellNum2 {
				return []*Cell{line1.cells[cellNums1[1-i]], line1.cells[cellNum1],
					line2.cells[cellNum2], line2.cells[cellNums2[1-j]]}
			}
		}
	}

	return []*Cell{}
}

/*
func (g *Grid) clearupSeenFromCells(cand int, cells []*Cell) (used bool) {
	if len(cells) != 2 {
		return
	}
	relCells := make([][]*Cell, 2)
	for i, cell := range cells {
		relCells[i] = append(relCells[i], g.boxes[cell.boxNum].cells[:]...)
		relCells[i] = append(relCells[i], g.rows[cell.rowNum].cells[:]...)
		relCells[i] = append(relCells[i], g.cols[cell.colNum].cells[:]...)
	}

	sharedCells := shareCells(relCells[0], relCells[1])

	for _, cell := range sharedCells {
		if equalCells(cell, cells[0]) || equalCells(cell, cells[1]) {
			continue
		}
		//fmt.Println("relcell", cell.rowNum, cell.colNum)
		if cell.removeCand(cand) {
			used = true
		}
	}

	return
}
*/

func (g *Grid) clearupSeenFromCells(cand int, cells []*Cell) (used bool) {
	return g.clearupCandsSeenFromCells([]int{cand}, cells)
}
func (g *Grid) clearupCandsSeenFromCells(cands []int, cells []*Cell) (used bool) {
	n := len(cells)
	relCells := make([][]*Cell, n)
	for i, cell := range cells {
		relCells[i] = append(relCells[i], g.boxes[cell.boxNum].cells[:]...)
		relCells[i] = append(relCells[i], g.rows[cell.rowNum].cells[:]...)
		relCells[i] = append(relCells[i], g.cols[cell.colNum].cells[:]...)
	}

	shared := relCells[0]
	for _, relCell := range relCells[1:] {
		shared = shareCells(shared, relCell)
	}

	for _, cell := range shared {
		if containCell(cells, cell) {
			continue
		}
		for _, cand := range cands {
			if cell.removeCand(cand) {
				used = true
			}
		}
	}

	return
}

func (g *Grid) TwoStringKite() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.twoStringKiteLines(cand) {
			used = true
		}
	}
	return
}

func (g *Grid) twoStringKiteLines(cand int) (used bool) {
	rows := chooseHousesWithCand(g.rows, cand)
	cols := chooseHousesWithCand(g.cols, cand)
	for _, row := range rows {
		for _, col := range cols {
			cells := verifyTwoStringKite(cand, g.rows[row], g.cols[col])
			if len(cells) != 4 {
				continue
			}
			if g.clearupSeenFromCells(cand, []*Cell{cells[0], cells[3]}) {
				used = true
				logTurbotFish(cand+1, cells, "Two String Kite")
			}
		}
	}
	return
}

func verifyTwoStringKite(cand int, row, col House) []*Cell {
	rowCellNums := intsFromBools(boolCellNums(row, cand))
	colCellNums := intsFromBools(boolCellNums(col, cand))
	if len(rowCellNums) != 2 || len(colCellNums) != 2 {
		return []*Cell{}
	}
	for i, rowCellNum := range rowCellNums {
		for j, colCellNum := range colCellNums {
			if (row.cells[rowCellNum] != col.cells[colCellNum]) && (row.cells[rowCellNum].boxNum == col.cells[colCellNum].boxNum) {
				return []*Cell{row.cells[rowCellNums[1-i]], row.cells[rowCellNum],
					col.cells[colCellNum], col.cells[colCellNums[1-j]]}
			}
		}
	}
	return []*Cell{}
}

func (g *Grid) TurbotFish() (used bool) {
	for cand := 0; cand < 9; cand++ {
		if g.turbotFishHouses(cand) {
			used = true
		}
	}
	return
}

func (g *Grid) turbotFishHouses(cand int) (used bool) {
	rows := chooseHousesWithCand(g.rows, cand)
	cols := chooseHousesWithCand(g.cols, cand)
	boxes := chooseHousesWithCand(g.boxes, cand)
	for _, box := range boxes {
		for _, row := range rows {
			cells := verifyTurbotFishBoxRow(cand, g.boxes[box], g.rows[row])
			if len(cells) != 4 {
				continue
			}
			if g.clearupSeenFromCells(cand, []*Cell{cells[0], cells[3]}) {
				used = true
				logTurbotFish(cand+1, cells, "Turbot Fish")
			}
		}
		for _, col := range cols {
			cells := verifyTurbotFishBoxCol(cand, g.boxes[box], g.cols[col])
			if len(cells) != 4 {
				continue
			}
			if g.clearupSeenFromCells(cand, []*Cell{cells[0], cells[3]}) {
				used = true
				logTurbotFish(cand+1, cells, "Turbot Fish")
			}
		}
	}
	return
}

func verifyTurbotFishBoxRow(cand int, box, row House) []*Cell {
	boxCellNums := intsFromBools(boolCellNums(box, cand))
	rowCellNums := intsFromBools(boolCellNums(row, cand))
	if len(rowCellNums) != 2 || len(boxCellNums) != 2 {
		return []*Cell{}
	}
	for i, boxCellNum := range boxCellNums {
		for j, rowCellNum := range rowCellNums {
			if (box.cells[boxCellNum] != row.cells[rowCellNum]) && (box.cells[boxCellNum].colNum == row.cells[rowCellNum].colNum) {
				return []*Cell{box.cells[boxCellNums[1-i]], box.cells[boxCellNum],
					row.cells[rowCellNum], row.cells[rowCellNums[1-j]]}
			}
		}
	}
	return []*Cell{}
}

func verifyTurbotFishBoxCol(cand int, box, col House) []*Cell {
	boxCellNums := intsFromBools(boolCellNums(box, cand))
	colCellNums := intsFromBools(boolCellNums(col, cand))
	if len(colCellNums) != 2 || len(boxCellNums) != 2 {
		return []*Cell{}
	}
	for i, boxCellNum := range boxCellNums {
		for j, colCellNum := range colCellNums {
			if (box.cells[boxCellNum] != col.cells[colCellNum]) && (box.cells[boxCellNum].rowNum == col.cells[colCellNum].rowNum) {
				return []*Cell{box.cells[boxCellNums[1-i]], box.cells[boxCellNum],
					col.cells[colCellNum], col.cells[colCellNums[1-j]]}
			}
		}
	}
	return []*Cell{}
}

/*
func (g *Grid) skycraperLines(lines [9]House) (used bool) {
	for i := 0; i < 8; i++ {
		candCells, candCounts := sumupCandCells(lines[i])
		for cand, count := range candCounts {
			if count != 2 {
				continue
			}
			cells := []*Cell{}
			for cellNum, candCell := range candCells[cand] {
				if candCell {
					cells = append(cells, lines[i].cells[cellNum])
				}
			}
			for j := i + 1; j < 9; j++ {
				yes, clearupCells := verifySkycraperLine(lines[j], cand, cells)
				if !yes {
					continue
				}
				if g.clearupSkycraper(cand, clearupCells) {
					used = true
					//fmt.Println("rm cell", cell.rowNum, cell.colNum)
					log.Printf("(r%vc%v, r%vc%v) %v %v", cells[0].rowNum, cells[0].colNum, cells[1].rowNum, cells[1].colNum, cand+1, "Skycraper")
				}
			}
		}
	}
	return
}

func verifySkycraperLine(line House, cand int, cells1 []*Cell) (yes bool, clearupCells []*Cell) {
	candCells, candCounts := sumupCandCells(line)
	if candCounts[cand] != 2 {
		return
	}

	cells2 := []*Cell{}
	for cellNum, candCell := range candCells[cand] {
		if candCell {
			cells2 = append(cells2, line.cells[cellNum])
		}
	}
	if cells1[0].rowNum == cells1[1].rowNum {
		if cells1[0].colNum == cells2[0].colNum {
			yes = true
			clearupCells = []*Cell{cells1[1], cells2[1]}
		}
		if cells1[1].colNum == cells2[1].colNum {
			yes = true
			clearupCells = []*Cell{cells1[0], cells2[0]}
		}
	} else { //cells1[0].colNum == cells1[1].colNum
		if cells1[0].rowNum == cells2[0].rowNum {
			yes = true
			clearupCells = []*Cell{cells1[1], cells2[1]}
		}
		if cells1[1].rowNum == cells2[1].rowNum {
			yes = true
			clearupCells = []*Cell{cells1[0], cells2[0]}
		}
	}

	return
}

func (g *Grid) Skycraper() (used bool) {
	if g.skycraperLines(g.rows) {
		used = true
	}
	if g.skycraperLines(g.cols) {
		used = true
	}
	return
}

type Chain struct {
	cand  int
	cells [2]*Cell
}

func findStrongChains(house House) (chains []*Chain) {
	candCells, candCounts := sumupCandCells(house)
	for cand, count := range candCounts {
		if count != 2 {
			continue
		}
		chain := &Chain{}
		chain.cand = cand
		n := 0
		for cellNum, candCell := range candCells[cand] {
			if candCell {
				chain.cells[n] = house.cells[cellNum]
				n++
			}
		}
		chains = append(chains, chain)
	}
	return
}

func findStrongChainCell(cand int, baseCell *Cell, house *House) *Cell {
	var found *Cell
	foundCount := 0
	for _, cell := range house.cells {
		if cell.solved() || equalCells(baseCell, cell) {
			continue
		}
		if cell.cands[cand] {
			found = cell
			foundCount++
		}
	}
	if foundCount != 1 {
		return nil
	}
	return found
}

func findWeakChainCells(cand int, baseCell *Cell, house *House) (weakCells []*Cell) {
	for _, cell := range house.cells {
		if cell.solved() || equalCells(baseCell, cell) {
			continue
		}
		if house.HouseType() == BoxHouse {
			if baseCell.rowNum == cell.rowNum || baseCell.colNum == cell.colNum {
				continue
			}
		} else {
			if baseCell.boxNum == cell.boxNum {
				continue
			}
		}

		if cell.cands[cand] {
			weakCells = append(weakCells, cell)
		}
	}
	return
}

func (g *Grid) findStrongChainCellClearup(chain *Chain) (used bool) {
	for i, cell := range chain.cells {
		weakCells := findWeakChainCells(chain.cand, cell, &g.boxes[cell.boxNum])
		for _, weakCell := range weakCells {
			strongCell := findStrongChainCell(chain.cand, weakCell, &g.cols[weakCell.colNum])
			if strongCell == nil {
				continue
			}
			if g.clearupSkycraper(chain.cand, []*Cell{strongCell, chain.cells[1-i]}) {
				used = true
			}
		}
	}
	return
}

func (g *Grid) twoStringLines(rows [9]House) (used bool) {
	for i := 0; i < 9; i++ {
		chains := findStrongChains(rows[i])
		for _, chain := range chains {
			if g.findStrongChainCellClearup(chain) {
				used = true
			}
		}
	}
	return
}

func (g *Grid) TwoStringKite() (used bool) {
	if g.twoStringLines(g.rows) {
		used = true
	}
	return
}

func (g *Grid) turbotFishClearup(chain *Chain) (used bool) {
	isChainRow := HouseType(chain.cells[0], chain.cells[1]) == RowHouse
	for i, cell := range chain.cells {
		var house *House
		if isChainRow {
			house = &g.cols[cell.colNum]
		} else {
			house = &g.rows[cell.rowNum]
		}
		weakCells := findWeakChainCells(chain.cand, cell, house)
		for _, weakCell := range weakCells {
			strongCell := findStrongChainCell(chain.cand, weakCell, &g.boxes[weakCell.boxNum])
			if strongCell == nil {
				continue
			}
			if g.clearupSkycraper(chain.cand, []*Cell{strongCell, chain.cells[1-i]}) {
				used = true
			}
		}
	}
	return
}

func (g *Grid) turbotFishLines(lines [9]House) (used bool) {
	for i := 0; i < 9; i++ {
		chains := findStrongChains(lines[i])
		for _, chain := range chains {
			if g.turbotFishClearup(chain) {
				used = true
			}
		}
	}
	return
}

func (g *Grid) TurbotFish() (used bool) {
	if g.turbotFishLines(g.rows) {
		used = true
	}

	if g.turbotFishLines(g.cols) {
		used = true
	}
	return
}
*/
